#!/bin/env python
# -*- coding: utf-8 -*-

#This file is part of pyLot library.
#
# pyLot is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyLot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pyLot.  If not, see <http://www.gnu.org/licenses/>.

__author__ = u'Pierre Puiseux, Guillaume Baty'
__copyright__ = u"Copyright 2011-2012 (C) andheo, Université de Pau et des Pays de l'Adour"
__credits__ = [u'Pierre Puiseux', u'Guillaume Baty']
__license__ = "GNU Lesser General Public License"

u"""
Interfaces thread/process
"""

__all__ = [u'ProcessHandlerInterface',
           u'LockHandlerInterface',
           u'LockInterface']

import time



from _pylot.minimal.path import Path
from _pylot.minimal.utils import check_type, error
from _pylot.minimal.commands import ShellCommand, PythonCommand
from _pylot.minimal.commandgroup import CommandGroup

from _pylot.interfaces.iohandler import IOHandlerInterface

class LockHandlerInterface (object):
  u"""
  Class used to create lock objects
  """

  def newLock (self):
    u"""
    Return a lock object inheriting LockInterface
    """
    raise NotImplementedError

class LockInterface (object):
  u"""
  Primitive Lock object.
  LockInterface API is inspired by 
  `thread.Lock<http://docs.python.org/library/threading.html#lock-objects>`_
  standard python object.

  A primitive lock is in one of two states, "locked" or "unlocked". 
  It is created in the unlocked state. 
  It has two basic methods, acquire() and release(). 
  When the state is unlocked, acquire() changes the state to locked and returns 
  immediately. When the state is locked, acquire() blocks until a call to 
  release() in another thread changes it to unlocked, then the acquire() 
  call resets it to locked and returns.
  """

  def acquire (self):
    raise NotImplementedError

  def release (self):
    raise NotImplementedError


class ProcessHandlerInterface (object):
  u"""
  Class used to run shell commands and python threads.
  
  Threads and commands are stored in a :class:`~_pylot.minimal.CommandGroup` 
  class. 
  This group can next be run by ProcessHandlerInterface, see
  :meth:`~_pylot.minimal.ProcessHandlerInterface.run` and 
  :meth:`~_pylot.minimal.ProcessHandlerInterface.runCmd`
  
  ProcessHandlerInterface provides properties to customise outputs and runs 

  You can change display level with 'io.level' attribute :
    - "silent": NOTHING is displayed (useful for doctests)
    - "standard": normal display
    - "verbose": add more informations
    - "debug": display all informations availables

  You can change shell output cleaning with autoclean attribute :
    - 'no':never clean outputs, 
    - 'error': clean error when start new group execution
    - 'output': like 'Error' but for output
    - 'all': clean all

  You can change maximum of simultaneous process with max_process property.
  Value must be an integer. This property has no effect if wait=True.

  You can change where outputs are displayed using "stdout" and "stderr"
  attributes. stdout is a list of keys.

  Current keys are :
    - "save": save outputs in variables
    - "write": send outputs to io writers
  Default: 
  ProcessHandlerInterface.stdout_mode = ['write']
  ProcessHandlerInterface.stderr_mode = ['write']

  :var stdout: where standard output is displayed
  :var stderr: where standard error is displayed

  Class also provides formatText methods to ensure text display consistency in
  ProcessHandlerInterface implementations.
  """

  def __init__(self, autoclean=u'no', wait=True, max_process=3) :
    u"""
    :param autoclean: see ProcessHandlerInterface documentation
    :type autoclean: str
    :param group: affect a group to process, if None, process is independent
    :type group: name

    ..todo:: 
      properties pour verbose, standard_output, ...
    """
    self._autoclean = autoclean
    self._wait = wait
    self._max_process = max_process
    self._stdout_mode = [u'write']
    self._stderr_mode = [u'write']

    self._wait_list = []

    self._init()

  def _init (self):
    self.io = IOHandlerInterface()

  def _setio (self, display_handler):
    self._display = display_handler

  io = property(
    fget=lambda self: self._display,
    fset=_setio
    )

  def _setstdout_mode (self, key_list):
    self._stdout_mode = self._checkOutputKeys(key_list)
    self._setStdoutMode(self._stdout_mode)

  def _setstderr_mode (self, key_list):
    self._stderr_mode = self._checkOutputKeys(key_list)
    self._setStderrMode(self._stderr_mode)

  stdout_mode = property(
    fget=lambda self: self._stdout_mode,
    fset=_setstdout_mode
    )

  stderr_mode = property(
    fget=lambda self: self._stderr_mode,
    fset=_setstderr_mode
    )

  def _setwait (self, value):
    check_type(u'wait', value, bool)
    self._wait = value
    self._setWait(self._wait)

  wait = property (
    fget=lambda self: self._wait,
    fset=_setwait)

  def _setmax_process (self, value):
    check_type(u'value', value, int)
    self._max_process = value
    self._setMaxProcess(self._max_process)

  max_process = property (
    fget=lambda self: self._max_process,
    fset=_setmax_process)

  def _setautoclean (self, value):
    check_type(u'value', value, unicode)
    self._autoclean = value
    self._setAutoclean(self._autoclean)

  autoclean = property (
    fget=lambda self: self._autoclean,
    fset=_setautoclean)

  def _setMaxProcess(self, value):
    u"""
    Additional instructions
    """
    pass

  def _setWait(self, value):
    u"""
    Additional instructions
    """
    pass

  def _setStderrMode(self, key_list):
    u"""
    Additional instructions
    """
    pass

  def _setStdoutMode(self, key_list):
    u"""
    Additional instructions
    """
    pass

  def _setAutoclean(self, key_list):
    u"""
    Additional instructions
    """
    pass

  def _checkOutputKeys (self, key_list):
    if key_list is None :
      key_list = [u'write']
    check_type(u'key_list', key_list, list)
    allowed_keys = (u'write', u'save')
    for key in key_list :
      if key not in allowed_keys :
        raise ValueError, error(tr_(u'Key %s not allowed. Available keys %s') % (
          key, u', '.join(allowed_keys)))
    if len(key_list) == 0 :
      key_list = [u'write']
    return key_list

  def _run(self, cmd_group, wait):
    raise NotImplementedError

  def _runCmd(self, cmd, args, working_directory, wait):
    raise NotImplementedError

  def _requireGroups(self, *args):
    raise NotImplementedError

  def _getRunningProcess(self):
    raise NotImplementedError

  def _getInQueueProcess(self):
    raise NotImplementedError

  def getRunningProcess(self):
    return self._getRunningProcess()

  def getInQueueProcess(self):
    return self._getInQueueProcess()

  def run(self, command_group, wait=None):
    u"""
    :param command_group: command group you want to run
    :type command_group: :class:`~_pylot.minimal.CommandGroup`
    :param wait: wait command_group run finish, [default: use ProcessHandler value]
    :type wait: bool or None
    """
    check_type(u'wait', wait, bool, none=True)
    check_type(u'command_group', command_group, CommandGroup)
    if wait is None :
      wait = self.wait
    command_group = self._run(command_group, wait)
    check_type(u'returned command_group', command_group, CommandGroup)

    return command_group

  def runCmd(self, cmd, args=[], cmd_id=None, alias=None,
             working_directory=None, wait=None):
    u"""
    Create a CommandGroup object with only one ShellCommand and run it
    """
    check_type(u'cmd', cmd, [unicode, ShellCommand, Path])
    cg = CommandGroup(u'command', u'Simple Command')
    if working_directory is not None :
      working_directory = Path(working_directory)
      if working_directory.isdir() :
        cg.working_directory = working_directory
    if isinstance(cmd, ShellCommand) :
      cg.addCmd(cmd)
    else :
      cg.addShellCmd(unicode(cmd), args, cmd_id, alias)
    return self.run(cg, wait)

  def clearQueueProcess(self):
    #TODO: écrire cette méthode / implement this method
    pass

  def killAllProcess(self):
    for process in self.getInQueueProcess() :
      process.kill()
    self.clearQueueProcess()
    for process in self.getRunningProcess() :
      process.kill()

  def requireGroups(self, *args):
    u"""
    Define required groups.
    ProcessHandlerInterface is locked until all required groups executions are not 
    successfully finished. If a required fails, ProcessHandlerInterface is unlocked
    and an error message is sent.
    
    :param args: one or more command groups
    :type args: CommandGroup
    """
    self._requireGroups(*args)

  def readSettings(self, settings, common=None):
    if common is None :
      common = settings
    common.beginGroup(u'ProcessHandler')
    self.wait = common.getValue(u"wait", self.wait).value
    self.max_process = common.getValue(u"max_process", self.max_process).value
    self.autoclean = common.getValue(u'autoclean', self.autoclean).value
    common.endGroup()
    self.io.readSettings(settings, common)

  def writeSettings(self, settings, common=None):
    if common is None :
      common = settings
    common.beginGroup(u'ProcessHandler')
    common.setValue(u"wait", self.wait)
    common.setValue(u"max_process", self.max_process)
    common.setValue(u'autoclean', self.autoclean)
    common.endGroup()
    self.io.writeSettings(settings, common)

  def formatTextCmdExecTime (self, command):
    u"""
    :return: text, style, level
    """
    if issubclass(type(command), (ShellCommand, PythonCommand)) :
      t = command.exec_time
      return tr_(u'"%(ID)s" execution time : %(TIME).4fs\n') % dict(
                ID=command.cmd_id, TIME=t), u'info', u'verbose'
    else :
      return u'', u'std', u'standard'

  def formatTextCmdGroupExecTime (self, command_group):
    u"""
    :return: (formated text, style, level)
    """
    t = command_group.exec_time
    return tr_(u'"%(ID)s" execution time : %(TIME).4fs\n') % dict(
               ID=command_group.group_id, TIME=t), u'info', u'standard'

  def formatTextCmdStarted (self, command):
    u"""
    :return: (formated text, style, level)
    """
    if issubclass(type(command), ShellCommand) :
      pwd = unicode(command.working_directory.relpath())
      if len(pwd.split(u'..')) > 2 :
        pwd = command.working_directory

      return (tr_(u'start shell command "%s" :\n - pwd: %s\n - cmd: %s\n') % (
        command.cmd_id, pwd, command.shellUString()),
        u'info', u'verbose')
    elif issubclass(type(command), PythonCommand) :
      return tr_(u'start python command "%s"\n') % (command.cmd_id), u'info', u'verbose'
    else :
      return tr_(u'start command "%s"\n') % (command.cmd_id), u'info', u'debug'

  def formatTextCmdGroupStarted (self, command_group):
    u"""
    :return: (formated text, style, level)
    """
    cg = command_group
    t = time.localtime ()
    text = u'[%02d:%02d:%02d]' % (t.tm_hour, t.tm_min, t.tm_sec)
    return tr_(u'%(TEXT)s Started "%(ALIAS)s" (%(ID)s)') % dict(TEXT=text,
               ALIAS=cg.alias, ID=cg.group_id), u'title', u'standard'

  def formatTextCmdGroupFinished (self, command_group, rcode=0):
    u"""
    :return: (formated text, style, level)
    """
    cg = command_group
    t = time.localtime ()
    text = u'[%02d:%02d:%02d]' % (t.tm_hour, t.tm_min, t.tm_sec)
    if rcode == 0 :
      return tr_(u'%(TEXT)s %(ID)s finished !\n') % dict(TEXT=text,
                 ID=cg.group_id), u'warn', u'standard'
    else :
      return tr_(u'%(TEXT)s %(ID)s fails (return code = %(RCODE)s) !\n') % dict(
                 TEXT=text, ID=cg.group_id, RCODE=cg.return_code), u'warn', u'standard'

